﻿using NLog;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection.PortableExecutable;
using System.Text;
using System.Threading.Tasks;

namespace WAU.Utils
{
    /*
     * 参考：
     * 1.https://learn.microsoft.com/zh-cn/aspnet/core/mvc/models/file-uploads?view=aspnetcore-6.0#server-and-app-configuration
     * 2.https://www.wikiwand.com/en/List_of_file_signatures
     * 3.https://en.wikipedia.org/wiki/List_of_file_signatures
     */

    /// <summary>
    /// 用于验证文件是否合法
    /// </summary>
    public class FileValidatorUtils
    {
        /// <summary>
        /// Logger
        /// </summary>
        private static readonly Logger _logger = LogManager.GetCurrentClassLogger();

        /// <summary>
        /// 文件前几个字节的数据（用于标志文件类型）
        /// </summary>
        private static readonly Dictionary<string, List<byte[]>> _fileSignature = new Dictionary<string, List<byte[]>>
        {
            // 根据文件数据库，部分文件的前导字节中可能有不确定部分。
            // 因为未涉及到很多，而且验证起来较为复杂，因此只验证前导字节中确定的部分

            {
                ".jpg", new List<byte[]>
                {
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xDB },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01 },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xEE }
                }
            },
            {
                ".jpeg", new List<byte[]>
                {
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xDB },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE0, 0x00, 0x10, 0x4A, 0x46, 0x49, 0x46, 0x00, 0x01 },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xEE }
                }
            },
            {
                ".bmp", new List<byte[]>
                {
                    new byte[] { 0x42, 0x4D }
                }
            },
            {
                ".gif", new List<byte[]>
                {
                    new byte[] { 0x47, 0x49, 0x46, 0x38, 0x37, 0x61 },
                    new byte[] { 0x47, 0x49, 0x46, 0x38, 0x39, 0x61 }
                }
            },
            {
                ".png", new List<byte[]>
                {
                    new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A },
                }
            },
            {
                ".tif", new List<byte[]>
                {
                    new byte[] { 0x49, 0x49, 0x2A, 0x00 },
                    new byte[] { 0x4D, 0x4D, 0x00, 0x2A },
                }
            },
            {
                ".tiff", new List<byte[]>
                {
                    new byte[] { 0x49, 0x49, 0x2A, 0x00 },
                }
            }
        };

        /// <summary>
        /// 判断文件是否合法
        /// </summary>
        /// <param name="fileFullPath"></param>
        /// <returns></returns>
        public static bool IsFileValidate(string fileFullPath)
        {
            FileInfo fi = new FileInfo(fileFullPath);
            if (!fi.Exists)
            {
                _logger.Warn($"文件 {fileFullPath} 不存在");
                return false;
            }

            if (!fileFullPath.Contains('.'))
            {
                _logger.Warn($"文件 {fileFullPath} 不存在扩展名，无法验证");
                return false;
            }

            string ext = fi.Extension.ToLower();

            if (!_fileSignature.ContainsKey(ext))
            {
                _logger.Warn($"未能找到扩展名 {ext} 对应的判断定义");
                return false;
            }

            using (FileStream fs = new FileStream(fileFullPath, FileMode.Open, FileAccess.Read))
            using (BinaryReader reader = new BinaryReader(fs))
            {
                List<byte[]> signatures = _fileSignature[ext];
                byte[] headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));

                return signatures.Any(signature => headerBytes.Take(signature.Length).SequenceEqual(signature));
            }
        }

        /// <summary>
        /// 判断文件是否合法（适用于读取并写入文件时判断，如果文件合法会将读取到的部分写入输出流中，以便后续操作可以继续）
        /// </summary>
        /// <param name="ext">扩展名（需要包含 .）</param>
        /// <param name="inputStream">还未读取的输入流</param>
        /// <param name="outputStream">要读取到的输出流</param>
        /// <returns></returns>
        public static bool IsFileValidate(string ext, Stream inputStream, Stream outputStream)
        {
            ext = ext.ToLower();

            if (!_fileSignature.ContainsKey(ext))
            {
                _logger.Warn($"未能找到扩展名 {ext} 对应的判断定义");
                return false;
            }

            List<byte[]> signatures = _fileSignature[ext];

            // inputStream 和 outputStream 由调用方管理，因此不使用 using 进行资源释放

            BinaryReader reader = new BinaryReader(inputStream);
            byte[] headerBytes = reader.ReadBytes(signatures.Max(m => m.Length));
            bool isValid = signatures.Any(signature => headerBytes.Take(signature.Length).SequenceEqual(signature));

            if (isValid)
            {
                BinaryWriter writer = new BinaryWriter(outputStream);
                writer.Write(headerBytes);
            }

            return isValid;
        }
    }
}
